﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EnvDTE;
using System.Runtime.InteropServices;
using System.IO;
using Process = System.Diagnostics.Process;
using Thread = System.Threading.Thread;
using Debugger = System.Diagnostics.Debugger;
using System.Threading;
using AbstraX.Contracts;
using System.Diagnostics;
using System.Runtime.Serialization;

namespace VSService
{
    public delegate void SessionDeadHandler(VSServiceSession session);

    [DataContract]
    public class VSServiceSession : IDisposable
    {
        private const string rootUserPath = @"C:\inetpub\wwwroot\Ken\";
        [DataMember]
        public Guid UserID { get; set; }
        private DTE dte;
        private Thread watchThread;
        private bool disposing;
        private bool released;
        private Timer initTimer;
        [DataMember]
        public int InstanceID { get; set; }
        [DataMember]
        public int ProcessID { get; set; }
        public Process Process { get; internal set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public bool IsRegistered { get; set; }
        [DataMember]
        public DateTime? SessionOpened { get; set; }
        [DataMember]
        public DateTime? VSInstanceOpened { get; set; }
        [DataMember]
        public DateTime? VSInstanceRegistered { get; set; }
        [DataMember]
        public DateTime? SessionClosed { get; set; }
        [DataMember]
        public DateTime? LastMessage { get; set; }
        [DataMember]
        public DateTime? LastMessageRequest { get; set; }
        public Queue<VSMessage> MessageQueue { get; internal set; }
        public ManualResetEvent CallEvent { get; internal set; }
        public object CallReturn { get; set; }
        public event SessionDeadHandler SessionDead;

        public VSServiceSession()
        {
        }

        public VSServiceSession(Process process)
        {
            this.ProcessID = process.Id;
            this.VSInstanceOpened = process.StartTime;
            this.Title = process.MainWindowTitle;
            this.InstanceID = this.GetHashCode();
        }

        public VSServiceSession(Guid userID)
        {
            this.UserID = userID;
            this.MessageQueue = new Queue<VSMessage>();
            this.CallEvent = new ManualResetEvent(false);
            this.InstanceID = this.GetHashCode();
        }

        private void TimerCallback(object state)
        {
            if (Monitor.TryEnter(this))
            {
                try
                {
                    if (this.IsRegistered)
                    {
                        initTimer.Dispose();
                    }
                    else
                    {
                        var file = new FileInfo(rootUserPath + UserID.ToString() + ".sln");
                        var solution = dte.Solution;

                        if (file.Exists)
                        {
                            if (solution.IsOpen)
                            {
                                solution.Close();
                            }

                            solution.Open(file.FullName);

                            initTimer.Dispose();
                            initTimer = new Timer(TimerCallback, null, 30000, 30000);
                        }
                    }
                }
                catch (Exception ex)
                {
                    // eat the error

                    Debug.WriteLine(ex.Message);
                }

                Monitor.Exit(this);
            }
        }

        public void OpenVisualStudio()
        {
            File.Copy(rootUserPath + "TemplateUserSolution.sln", rootUserPath + UserID.ToString() + ".sln", true);

            var type = Type.GetTypeFromProgID("VisualStudio.DTE.10.0");
            var currentInstances = Process.GetProcesses().Where(p => p.ProcessName == "devenv");
            
            dte = (DTE)Activator.CreateInstance(type);

            var newInstances = Process.GetProcesses().Where(p => p.ProcessName == "devenv" && p.MainWindowHandle.ToInt32() == 0 && !currentInstances.Any(cp => cp.Id == p.Id));

            if (newInstances.Count() > 1 || newInstances.Count() == 0)
            {
                Debugger.Break();
            }

            this.Process = newInstances.Single();
            this.ProcessID = this.Process.Id;

            // give instance time to hook events

            initTimer = new Timer(TimerCallback, null, 1000, 1000);

            watchThread = new Thread(WatchThreadProc);

            watchThread.Priority = ThreadPriority.Lowest;
            watchThread.Start();
        }

        private void WatchThreadProc()
        {
            while (!disposing)
            {
                if (this.ProcessID != 0)
                {
                    if (Process.GetProcesses().Where(p => p.ProcessName == "devenv").Where(p => p.Id == this.ProcessID).Any())
                    {
                        if (this.Process == null)
                        {
                            this.Process = Process.GetProcesses().Where(p => p.ProcessName == "devenv").Where(p => p.Id == this.ProcessID).Single();
                        }
                    }
                    else
                    {
                        SessionDead(this);
                    }
                }

                if (this.VSInstanceOpened != null && this.VSInstanceRegistered == null && (DateTime.Now - this.VSInstanceOpened > TimeSpan.FromMinutes(10)))
                {
                    this.Dispose();
                    this.Process.Kill();
                }
                else if (this.VSInstanceRegistered != null && (DateTime.Now - this.LastMessageRequest > TimeSpan.FromMinutes(10)))
                {
                    this.Dispose();
                }

                Thread.Sleep(100);
            }

            while (!released)
            {
                if (DateTime.Now - this.SessionClosed > TimeSpan.FromSeconds(5))
                {
                    this.Process.Kill();
                }
            }
        }

        public void Dispose()
        {
            this.SessionClosed = DateTime.Now;
            disposing = true;

            dte.Quit();
            Marshal.ReleaseComObject(dte);

            released = true;

            while (watchThread.IsAlive)
            {
                Thread.Sleep(100);

                if (DateTime.Now - this.SessionClosed > TimeSpan.FromSeconds(5))
                {
                    watchThread.Abort();
                }
            }

            var exitCheck = DateTime.Now;

            while (!this.Process.HasExited)
            {
                if (DateTime.Now - exitCheck > TimeSpan.FromMinutes(1))
                {
                    this.Process.Kill();
                }

                Thread.Sleep(1000);
            }
        }
    }
}